热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

频次|差值_内存耗尽后Redis会发生什么?

篇首语:本文由编程笔记#小编为大家整理,主要介绍了内存耗尽后Redis会发生什么?相关的知识,希望对你有一定的参考价值。前言

篇首语:本文由编程笔记#小编为大家整理,主要介绍了内存耗尽后 Redis 会发生什么?相关的知识,希望对你有一定的参考价值。



前言

作为一台服务器来说,内存并不是无限的,所以总会存在内存耗尽的情况,那么当 Redis 服务器的内存耗尽后,如果继续执行请求命令,Redis 会如何处理呢?


内存回收

使用Redis 服务时,很多情况下某些键值对只会在特定的时间内有效,为了防止这种类型的数据一直占有内存,我们可以给键值对设置有效期。Redis 中可以通过 4 个独立的命令来给一个键设置过期时间:


  • expire key ttl:将 key 值的过期时间设置为 ttl 
  • pexpire key ttl:将 key 值的过期时间设置为 ttl 毫秒
  • expireat key timestamp:将 key 值的过期时间设置为指定的 timestamp 秒数
  • pexpireat key timestamp:将 key 值的过期时间设置为指定的 timestamp 毫秒数

PS:不管使用哪一个命令,最终 Redis 底层都是使用 pexpireat 命令来实现的。另外,set 等命令也可以设置 key 的同时加上过期时间,这样可以保证设值和设过期时间的原子性。

设置了有效期后,可以通过 ttl 和 pttl 两个命令来查询剩余过期时间(如果未设置过期时间则下面两个命令返回 -1,如果设置了一个非法的过期时间,则都返回 -2):


  • ttl key 返回 key 剩余过期秒数。
  • pttl key 返回 key 剩余过期的毫秒数。

过期策略

如果将一个过期的键删除,我们一般都会有三种策略:


  • 定时删除:为每个键设置一个定时器,一旦过期时间到了,则将键删除。这种策略对内存很友好,但是对 CPU 不友好,因为每个定时器都会占用一定的 CPU 资源。
  • 惰性删除:不管键有没有过期都不主动删除,等到每次去获取键时再判断是否过期,如果过期就删除该键,否则返回键对应的值。这种策略对内存不够友好,可能会浪费很多内存。
  • 定期扫描:系统每隔一段时间就定期扫描一次,发现过期的键就进行删除。这种策略相对来说是上面两种策略的折中方案,需要注意的是这个定期的频率要结合实际情况掌控好,使用这种方案有一个缺陷就是可能会出现已经过期的键也被返回。

在 Redis 当中,其选择的是策略 2 和策略 3 的综合使用。不过 Redis 的定期扫描只会扫描设置了过期时间的键,因为设置了过期时间的键 Redis 会单独存储,所以不会出现扫描所有键的情况:

typedef struct redisDb
dict *dict; //所有的键值对
dict *expires; //设置了过期时间的键值对
dict *blocking_keys; //被阻塞的key,如客户端执行BLPOP等阻塞指令时
dict *watched_keys; //WATCHED keys
int id; //Database ID
//... 省略了其他属性
redisDb;

8 种淘汰策略

假如 Redis 当中所有的键都没有过期,而且此时内存满了,那么客户端继续执行 set 等命令时 Redis 会怎么处理呢?Redis 当中提供了不同的淘汰策略来处理这种场景。

首先 Redis 提供了一个参数 maxmemory 来配置 Redis 最大使用内存:

maxmemory

或者也可以通过命令 config set maxmemory 1GB 来动态修改。

如果没有设置该参数,那么在 32 位的操作系统中 Redis 最多使用 3GB 内存,而在 64 位的操作系统中则不作限制。

Redis 中提供了 8 种淘汰策略,可以通过参数 maxmemory-policy 进行配置:


淘汰策略说明
volatile-lru根据 LRU 算法删除设置了过期时间的键,直到腾出可用空间。如果没有可删除的键对象,且内存还是不够用时,则报错
allkeys-lru根据 LRU 算法删除所有的键,直到腾出可用空间。如果没有可删除的键对象,且内存还是不够用时,则报错
volatile-lfu根据 LFU 算法删除设置了过期时间的键,直到腾出可用空间。如果没有可删除的键对象,且内存还是不够用时,则报错
allkeys-lfu根据 LFU 算法删除所有的键,直到腾出可用空间。如果没有可删除的键对象,且内存还是不够用时,则报错
volatile-random随机删除设置了过期时间的键,直到腾出可用空间。如果没有可删除的键对象,且内存还是不够用时,则报错
allkeys-random随机删除所有键,直到腾出可用空间。如果没有可删除的键对象,且内存还是不够用时,则报错
volatile-ttl根据键值对象的 ttl 属性, 删除最近将要过期数据。 如果没有,则直接报错
noeviction默认策略,不作任何处理,直接报错

PS&#xff1a;淘汰策略也可以直接使用命令 config set maxmemory-policy <策略> 来进行动态配置。


LRU 算法

LRU 全称为&#xff1a;Least Recently Used。即&#xff1a;最近最长时间未被使用。这个主要针对的是使用时间。


Redis 改进后的 LRU 算法

在 Redis 当中&#xff0c;并没有采用传统的 LRU 算法&#xff0c;因为传统的 LRU 算法存在 2 个问题&#xff1a;


  • 需要额外的空间进行存储。
  • 可能存在某些 key 值使用很频繁&#xff0c;但是最近没被使用&#xff0c;从而被 LRU 算法删除。

为了避免以上 2 个问题&#xff0c;Redis 当中对传统的 LRU 算法进行了改造&#xff0c;通过抽样的方式进行删除

配置文件中提供了一个属性 maxmemory_samples 5&#xff0c;默认值就是 5&#xff0c;表示随机抽取 5 个 key 值&#xff0c;然后对这 5 个 key值按照 LRU 算法进行删除&#xff0c;所以很明显&#xff0c;key 值越大&#xff0c;删除的准确度越高。

对抽样 LRU 算法和传统的 LRU 算法&#xff0c;Redis 官网当中有一个对比图&#xff1a;


  • 浅灰色带是被删除的对象。

  • 灰色带是未被删除的对象。

  • 绿色是添加的对象。

左上角第一幅图代表的是传统 LRU 算法&#xff0c;可以看到&#xff0c;当抽样数达到 10 个&#xff08;右上角&#xff09;&#xff0c;已经和传统的 LRU 算法非常接近了。


Redis 如何管理热度数据

前面我们讲述字符串对象时&#xff0c;提到了 redisObject 对象中存在一个 lru 属性&#xff1a;

typedef struct redisObject
unsigned type:4;//对象类型&#xff08;4位&#61;0.5字节&#xff09;
unsigned encoding:4;//编码&#xff08;4位&#61;0.5字节&#xff09;
unsigned lru:LRU_BITS;//记录对象最后一次被应用程序访问的时间&#xff08;24位&#61;3字节&#xff09;
int refcount;//引用计数。等于0时表示可以被垃圾回收&#xff08;32位&#61;4字节&#xff09;
void *ptr;//指向底层实际的数据存储结构&#xff0c;如&#xff1a;SDS等(8字节)
robj;

lru 属性是创建对象的时候写入&#xff0c;对象被访问到时也会进行更新。正常人的思路就是最后决定要不要删除某一个键肯定是用当前时间戳减去 lru&#xff0c;差值最大的就优先被删除。但是 Redis 里面并不是这么做的&#xff0c;Redis 中维护了一个全局属性 lru_clock&#xff0c;这个属性是通过一个全局函数 serverCron 每隔 100 毫秒执行一次来更新的&#xff0c;记录的是当前 unix 时间戳。

最后决定删除的数据是通过 lru_clock 减去对象的 lru 属性而得出的。那么为什么 Redis 要这么做呢&#xff1f;直接取全局时间不是更准确吗&#xff1f;

这是因为这么做可以避免每次更新对象的 lru 属性的时候可以直接取全局属性&#xff0c;而不需要去调用系统函数来获取系统时间&#xff0c;从而提升效率&#xff08;Redis 当中有很多这种细节考虑来提升性能&#xff0c;可以说是对性能尽可能的优化到极致&#xff09;。

不过这里还有一个问题&#xff0c;我们看到&#xff0c;redisObject 对象中的 lru 属性只有 24 位&#xff0c;24 位只能存储 194 天的时间戳大小&#xff0c;一旦超过 194 天之后就会重新从 0 开始计算&#xff0c;所以这时候就可能会出现 redisObject 对象中的 lru 属性大于全局的 lru_clock 属性的情况。

正因为如此&#xff0c;所以计算的时候也需要分为 2 种情况&#xff1a;


  • 当全局 lruclock > lru&#xff0c;则使用 lruclock - lru 得到空闲时间。
  • 当全局 lruclock < lru&#xff0c;则使用 lruclock_max&#xff08;即 194 天&#xff09; - lru &#43; lruclock 得到空闲时间。

需要注意的是&#xff0c;这种计算方式并不能保证抽样的数据中一定能删除空闲时间最长的。这是因为首先超过 194 天还不被使用的情况很少&#xff0c;再次只有 lruclock 第 2 轮继续超过 lru 属性时&#xff0c;计算才会出问题。

比如对象 A 记录的 lru 是 1 天&#xff0c;而 lruclock 第二轮都到 10 天了&#xff0c;这时候就会导致计算结果只有 10-1&#61;9 天&#xff0c;实际上应该是 194&#43;10-1&#61;203 天。但是这种情况可以说又是更少发生&#xff0c;所以说这种处理方式是可能存在删除不准确的情况&#xff0c;但是本身这种算法就是一种近似的算法&#xff0c;所以并不会有太大影响。


LFU 算法

LFU 全称为&#xff1a;Least Frequently Used。即&#xff1a;最近最少频率使用&#xff0c;这个主要针对的是使用频率。这个属性也是记录在redisObject 中的 lru 属性内。

当我们采用 LFU 回收策略时&#xff0c;lru 属性的高 16 位用来记录访问时间&#xff08;last decrement time&#xff1a;ldt&#xff0c;单位为分钟&#xff09;&#xff0c;低 8 位用来记录访问频率&#xff08;logistic counter&#xff1a;logc&#xff09;&#xff0c;简称 counter


访问频次递增

LFU 计数器每个键只有 8 位&#xff0c;它能表示的最大值是 255&#xff0c;所以 Redis 使用的是一种基于概率的对数器来实现 counter 的递增。r

给定一个旧的访问频次&#xff0c;当一个键被访问时&#xff0c;counter 按以下方式递增&#xff1a;


  1. 提取 0 和 1 之间的随机数 R
  2. counter - 初始值&#xff08;默认为 5&#xff09;&#xff0c;得到一个基础差值&#xff0c;如果这个差值小于 0&#xff0c;则直接取 0&#xff0c;为了方便计算&#xff0c;把这个差值记为 baseval
  3. 概率 P 计算公式为&#xff1a;1/(baseval * lfu_log_factor &#43; 1)
  4. 如果 R  时&#xff0c;频次进行递增&#xff08;counter&#43;&#43;&#xff09;。

公式中的 lfu_log_factor 称之为对数因子&#xff0c;默认是 10 &#xff0c;可以通过参数来进行控制&#xff1a;

lfu_log_factor 10

下图就是对数因子 lfu_log_factor 和频次 counter 增长的关系图&#xff1a;

可以看到&#xff0c;当对数因子 lfu_log_factor 为 100 时&#xff0c;大概是 10M&#xff08;1000万&#xff09; 次访问才会将访问 counter 增长到 255&#xff0c;而默认的 10 也能支持到 1M&#xff08;100万&#xff09; 次访问 counter 才能达到 255 上限&#xff0c;这在大部分场景都是足够满足需求的。


访问频次递减

如果访问频次 counter 只是一直在递增&#xff0c;那么迟早会全部都到 255&#xff0c;也就是说 counter 一直递增不能完全反应一个 key 的热度的&#xff0c;所以当某一个 key 一段时间不被访问之后&#xff0c;counter 也需要对应减少。

counter 的减少速度由参数 lfu-decay-time 进行控制&#xff0c;默认是 1&#xff0c;单位是分钟。默认值 1 表示&#xff1a;N 分钟内没有访问&#xff0c;counter 就要减 N

lfu-decay-time 1

具体算法如下&#xff1a;


  1. 获取当前时间戳&#xff0c;转化为分钟后取低 16 位&#xff08;为了方便后续计算&#xff0c;这个值记为 now&#xff09;。
  2. 取出对象内的 lru 属性中的高 16 位&#xff08;为了方便后续计算&#xff0c;这个值记为 ldt&#xff09;。
  3. 当 lru > now 时&#xff0c;默认为过了一个周期&#xff08;16 位&#xff0c;最大 65535)&#xff0c;则取差值 65535-ldt&#43;now&#xff1a;当 lru <&#61; now时&#xff0c;取差值 now-ldt&#xff08;为了方便后续计算&#xff0c;这个差值记为 idle_time &#xff09;。
  4. 取出配置文件中的 lfu_decay_time 值&#xff0c;然后计算&#xff1a;idle_time / lfu_decay_time&#xff08;为了方便后续计算&#xff0c;这个值记为num_periods&#xff09;。
  5. 最后将counter减少&#xff1a;counter - num_periods

看起来这么复杂&#xff0c;其实计算公式就是一句话&#xff1a;取出当前的时间戳和对象中的 lru 属性进行对比&#xff0c;计算出当前多久没有被访问到&#xff0c;比如计算得到的结果是 100 分钟没有被访问&#xff0c;然后再去除配置参数 lfu_decay_time&#xff0c;如果这个配置默认为 1也即是 100/1&#61;100&#xff0c;代表 100 分钟没访问&#xff0c;所以 counter 就减少 100


总结

本文主要介绍了 Redis 过期键的处理策略&#xff0c;以及当服务器内存不够时 Redis 的 8 种淘汰策略&#xff0c;最后介绍了 Redis 中的两种主要的淘汰算法 LRU 和 LFU

参考&#xff1a;

https://www.cnblogs.com/lonely-wolf/p/14403264.html



推荐阅读
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • 本文介绍了在rhel5.5操作系统下搭建网关+LAMP+postfix+dhcp的步骤和配置方法。通过配置dhcp自动分配ip、实现外网访问公司网站、内网收发邮件、内网上网以及SNAT转换等功能。详细介绍了安装dhcp和配置相关文件的步骤,并提供了相关的命令和配置示例。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了在Linux下安装和配置Kafka的方法,包括安装JDK、下载和解压Kafka、配置Kafka的参数,以及配置Kafka的日志目录、服务器IP和日志存放路径等。同时还提供了单机配置部署的方法和zookeeper地址和端口的配置。通过实操成功的案例,帮助读者快速完成Kafka的安装和配置。 ... [详细]
  • 服务器上的操作系统有哪些,如何选择适合的操作系统?
    本文介绍了服务器上常见的操作系统,包括系统盘镜像、数据盘镜像和整机镜像的数量。同时,还介绍了共享镜像的限制和使用方法。此外,还提供了关于华为云服务的帮助中心,其中包括产品简介、价格说明、购买指南、用户指南、API参考、最佳实践、常见问题和视频帮助等技术文档。对于裸金属服务器的远程登录,本文介绍了使用密钥对登录的方法,并提供了部分操作系统配置示例。最后,还提到了SUSE云耀云服务器的特点和快速搭建方法。 ... [详细]
  • 本文介绍了解决Netty拆包粘包问题的一种方法——使用特殊结束符。在通讯过程中,客户端和服务器协商定义一个特殊的分隔符号,只要没有发送分隔符号,就代表一条数据没有结束。文章还提供了服务端的示例代码。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 本文介绍了在使用Laravel和sqlsrv连接到SQL Server 2016时,如何在插入查询中使用输出子句,并返回所需的值。同时讨论了使用CreatedOn字段返回最近创建的行的解决方法以及使用Eloquent模型创建后,值正确插入数据库但没有返回uniqueidentifier字段的问题。最后给出了一个示例代码。 ... [详细]
  • 上图是InnoDB存储引擎的结构。1、缓冲池InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可以看作是基于磁盘的数据库系统。在数据库系统中,由于CPU速度 ... [详细]
  • Summarize function is doing alignment without timezone ?
    Hi.Imtryingtogetsummarizefrom00:00otfirstdayofthismonthametric, ... [详细]
author-avatar
--Fac_k-
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有